#include "SDL.h"
#include "GL/glew.h"

namespace {

const char *g_srcVS =
"#version 150 core\n"
"in vec4 in_vertex;"
"void main() {"
    "gl_Position = in_vertex;"
"}";

const char *g_srcFS =
"#version 150 core\n"
//
// Description : Array and textureless GLSL 2D/3D/4D simplex
// noise functions.
// Author : Ian McEwan, Ashima Arts.
// Maintainer : ijm
// Lastmod : 20110822 (ijm)
// License : Copyright (C) 2011 Ashima Arts. All rights reserved.
// Distributed under the MIT License. See LICENSE file.
// https://github.com/ashima/webgl-noise
//
"vec3 mod289(vec3 x) {"
    "return x - floor(x * (1.0 / 289.0)) * 289.0;"
"}"
"vec4 mod289(vec4 x) {"
    "return x - floor(x * (1.0 / 289.0)) * 289.0;"
"}"
"vec4 permute(vec4 x) {"
    "return mod289(((x*34.0)+1.0)*x);"
"}"
"vec4 taylorInvSqrt(vec4 r) {"
    "return 1.79284291400159 - 0.85373472095314 * r;"
"}"
"float snoise(vec3 v) {"
    "const vec2 C = vec2(1.0/6.0, 1.0/3.0);"
    "const vec4 D = vec4(0.0, 0.5, 1.0, 2.0);"
    "vec3 i = floor(v + dot(v, C.yyy));"
    "vec3 x0 = v - i + dot(i, C.xxx);"
    "vec3 g = step(x0.yzx, x0.xyz);"
    "vec3 l = 1.0 - g;"
    "vec3 i1 = min(g.xyz, l.zxy);"
    "vec3 i2 = max(g.xyz, l.zxy);"
    "vec3 x1 = x0 - i1 + C.xxx;"
    "vec3 x2 = x0 - i2 + C.yyy;"
    "vec3 x3 = x0 - D.yyy;"
    "i = mod289(i);"
    "vec4 p = permute(permute(permute(i.z + vec4(0.0, i1.z, i2.z, 1.0)) + i.y + vec4(0.0, i1.y, i2.y, 1.0)) + i.x + vec4(0.0, i1.x, i2.x, 1.0));"
    "float n_ = 0.142857142857;"
    "vec3 ns = n_ * D.wyz - D.xzx;"
    "vec4 j = p - 49.0 * floor(p * ns.z * ns.z);"
    "vec4 x_ = floor(j * ns.z);"
    "vec4 y_ = floor(j - 7.0 * x_);"
    "vec4 x = x_ *ns.x + ns.yyyy;"
    "vec4 y = y_ *ns.x + ns.yyyy;"
    "vec4 h = 1.0 - abs(x) - abs(y);"
    "vec4 b0 = vec4(x.xy, y.xy);"
    "vec4 b1 = vec4(x.zw, y.zw);"
    "vec4 s0 = floor(b0)*2.0 + 1.0;"
    "vec4 s1 = floor(b1)*2.0 + 1.0;"
    "vec4 sh = -step(h, vec4(0.0));"
    "vec4 a0 = b0.xzyw + s0.xzyw*sh.xxyy;"
    "vec4 a1 = b1.xzyw + s1.xzyw*sh.zzww;"
    "vec3 p0 = vec3(a0.xy,h.x);"
    "vec3 p1 = vec3(a0.zw,h.y);"
    "vec3 p2 = vec3(a1.xy,h.z);"
    "vec3 p3 = vec3(a1.zw,h.w);"
    "vec4 norm = taylorInvSqrt(vec4(dot(p0, p0), dot(p1, p1), dot(p2, p2), dot(p3, p3)));"
    "p0 *= norm.x;"
    "p1 *= norm.y;"
    "p2 *= norm.z;"
    "p3 *= norm.w;"
    "vec4 m = max(0.6 - vec4(dot(x0, x0), dot(x1, x1), dot(x2, x2), dot(x3, x3)), 0.0);"
    "m = m*m;"
    "return 42.0 * dot(m*m, vec4(dot(p0, x0), dot(p1, x1), dot(p2, x2), dot(p3, x3)));"
"}"
"uniform float t;"
"float box(vec3 p, vec3 b, float r) {"
    "return length(max(abs(p) - b, 0.0)) - r;"
"}"
"float map(in vec3 p, out int matID) {"
    "float dist, minDist = 100.0;"
    "dist = box(p, vec3(1), 0.03);"
    "if (dist < minDist) {"
        "minDist = dist;"
        "matID = 1;"
    "}"
    "dist = (length(p) - 1.25);"
    "if (dist < minDist) {"
        "minDist = dist;"
        "matID = 2;"
    "}"
    "dist = dot(p, vec3(0, 1, 0)) + 1.0 + 0.1*snoise(p);"
    "if (dist < minDist) {"
        "minDist = dist;"
        "matID = 3;"
    "}"
    "return minDist;"
"}"
"float is(in vec3 ro, in vec3 rd, out int matID) {"
    "for (float t = 0.0; t < 10.0;) {"
        "float d = map(ro + rd*t, matID);"
        "if (d < 0.002)"
            "return t;"
        "t += d;"
    "}"
    "matID = -1;"
    "return -1.0;"
"}"
"float shadow(vec3 ro, vec3 rd) {"
    "float r = 1.0;"
    "int m;"
    "for (float t = 0.01; t < 10.0;) {"
        "float d = map(ro + rd*t, m);"
        "if (d < 0.001)"
            "return 0.0;"
        "r = min(r, 16.0*d/t);"
        "t += d;"
    "}"
    "return r;"
"}"
"vec3 computeNormal(vec3 p){"
    "int m;"
    "vec3 e=vec3(.001,0,0);"
    "return normalize(vec3(map(p+e.xyy,m)-map(p-e.xyy,m),map(p+e.yxy,m)-map(p-e.yxy,m),map(p+e.yyx,m)-map(p-e.yyx,m)));"
"}"
"out vec4 fs_color;"
"void main() {"
    "vec2 p = -1.0 + 2.0*gl_FragCoord.xy/vec2(1280, 720);"
    "p.x *= 1.77;"

    "vec3 ro=vec3(2.5*cos(.5),1.5,2.5*sin(.4*t));"
    "vec3 z=normalize(vec3(0)-ro);"
    "vec3 x=normalize(cross(vec3(0,1,0),z));"
    "vec3 y=normalize(cross(z,x));"
    "vec3 rd=normalize(x*p.x+y*p.y+z*1.5);"

    "vec3 c = vec3(0);"
    "int matID;"
    "float d = is(ro, rd, matID);"
    "if (d > 0.0) {"
        "vec3 p = ro + d*rd;"
        "vec3 n = computeNormal(p);"

        "vec3[4] diffuseCol = vec3[](vec3(1), vec3(0, 0.7, 0), vec3(1), vec3(0.9, 0.8, 0.2));"

        "vec3 lightDir0 = normalize(vec3(1, 0.8, -0.6));"
        "vec3 lightDir1 = normalize(vec3(-1.2, 1.9, 0.6));"

        "c += 0.7*max(0.4 + 0.6*dot(n, lightDir0)*shadow(p, lightDir0), 0.0)*diffuseCol[matID];"
        "c += 0.4*max(0.4 + 0.6*dot(n, lightDir1)*shadow(p, lightDir1), 0.0)*diffuseCol[matID];"
    "}"
    "fs_color = vec4(c, 1);"
"}";

SDL_WindowID g_window;
SDL_GLContext g_context;
GLuint g_program;
GLuint g_vao;
GLuint g_vbo;

bool Events() {
    SDL_Event evt;
    while (SDL_PollEvent(&evt)) {
        switch (evt.type) {
            case SDL_QUIT:
                return true; // quit
            case SDL_KEYDOWN:
                if (evt.key.keysym.sym == SDLK_ESCAPE) {
                    return true; // quit
                }
                break;
        }
    }

    return false;
}

int Frame() {
    glUseProgram(g_program);
    glUniform1f(glGetUniformLocation(g_program, "t"), 0.001f*SDL_GetTicks());
    glBindVertexArray(g_vao);
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

    return 0;
}

int Init() {
    // SDL
    SDL_LogSetPriority(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_DEBUG);

    if (SDL_Init(SDL_INIT_TIMER | SDL_INIT_VIDEO) < 0) {
        SDL_LogCritical(SDL_LOG_CATEGORY_APPLICATION, "SDL_Init failed.");
        return -1;
    }

    // window
    SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
    SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
    SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
    SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
    SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
    SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);

    const int w = 1280, h = 720;
    g_window = SDL_CreateWindow("test5", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, w, h, SDL_WINDOW_SHOWN | SDL_WINDOW_OPENGL | SDL_WINDOW_BORDERLESS);
    if (g_window == NULL) {
        SDL_LogCritical(SDL_LOG_CATEGORY_APPLICATION, "SDL_CreateWindow failed.");
        return -1;
    }

    // context
    g_context = SDL_GL_CreateContext(g_window);
    if (g_context == NULL) {
        SDL_LogCritical(SDL_LOG_CATEGORY_APPLICATION, "SDL_GL_CreateContext failed.");
        return -1;
    }

    if (glewInit() != GLEW_OK) {
        SDL_LogCritical(SDL_LOG_CATEGORY_APPLICATION, "glewInit failed.");
        return -1;
    }

    SDL_GL_SetSwapInterval(0);

    // vbo
    const float vertices[] = { -1.0f, -1.0f, -1.0f, 1.0f, 1.0f, -1.0f, 1.0f, 1.0f };
    glGenBuffers(1, &g_vbo);
    glBindBuffer(GL_ARRAY_BUFFER, g_vbo);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

    // vao
    glGenVertexArrays(1, &g_vao);
    glBindVertexArray(g_vao);
    glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, 0);
    glEnableVertexAttribArray(0);

    // program
    g_program = glCreateProgram();

    GLuint sh = glCreateShader(GL_VERTEX_SHADER);
    glAttachShader(g_program, sh);
    glDeleteShader(sh);
    glShaderSource(sh, 1, &g_srcVS, NULL);
    glCompileShader(sh);
    sh = glCreateShader(GL_FRAGMENT_SHADER);
    glAttachShader(g_program, sh);
    glDeleteShader(sh);
    glShaderSource(sh, 1, &g_srcFS, NULL);
    glCompileShader(sh);

    glLinkProgram(g_program);
    GLchar log[1024];
    GLsizei logLen;
    glGetProgramInfoLog(g_program, sizeof(log), &logLen, log);
    if (logLen > 0) {
        SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "\n%s", log);
    }

    return 0;
}

int Run() {
    while (true) {
        if (Events()) break;
        if (Frame() < 0) return -1;
        SDL_GL_SwapWindow(g_window);
    }

    return 0;
}

int Quit() {
    if (g_context) {
        glDeleteBuffers(1, &g_vbo);
        glDeleteVertexArrays(1, &g_vao);
        glDeleteProgram(g_program);

        SDL_GL_DeleteContext(g_context);
    }

    SDL_DestroyWindow(g_window);
    SDL_Quit();

    return 0;
}

} // namespace

int main(int argc, char *argv[]) {
    if (Init() == 0) {
        Run();
    }
    Quit();
    return 0;
}
